package com.onpositive.rest.model.printer;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Map;

import com.onpositive.rest.model.Body;
import com.onpositive.rest.model.Method;
import com.onpositive.rest.model.Param;
import com.onpositive.rest.model.Resource;
import com.onpositive.rest.model.Response;

public class ModelPrinter {
	
	private static final String TAG_REQUIRED = "required";

	private static final String TAG_SCHEMA = "schema";

	private static final String INDENT_INCREMENT = "  ";
	
	private final static String lineSeparator = System.getProperty("line.separator");
	
	private static final String TAG_EXAMPLE = "example";
	private final static String TAG_DESCRIPTION="description";
	private static final String TAG_RESPONSES = "responses";
	private static final String TAG_BODY = "body";
	private static final String TAG_WWW_FORM_URLENCODED = "application/x-www-form-urlencoded";
	
	public static void printResource(Resource resource, String baseIndent, OutputStream stream){
		
		if(stream==null)
			return;
		
		String indent = baseIndent;
		for(int i = 0 ; i < resource.getLevel();i++ ){
			indent += INDENT_INCREMENT;
		}
		
		String indent1 = indent+INDENT_INCREMENT;
		String indent2 = indent1+INDENT_INCREMENT;		
		
		String uri = resource.getUrl();
		if(!uri.startsWith("/"))
			uri=uri+"/";
		
		String description = resource.getDescription();		
		try {
			writeTagString(uri, indent, stream,false);
			if(nonEmptyString(description)){
				writeTagString(TAG_DESCRIPTION, indent1, stream,true);
				writePassage(description, indent2,stream);
			}
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		LinkedHashMap<String, Method> methods = resource.getMethods();
		if(methods!=null&&!methods.isEmpty()){
			for(Method method:methods.values()){
				printMethod(method,indent1,stream);
			}			
		}
		for(Resource chRes: resource.getChildResources().values()){
			printResource(chRes, baseIndent, stream);
		}
	}

	private static void printMethod(Method method, String indent, OutputStream stream)
	{
		String httpType = method.getHttpType().name().toLowerCase();
		String description = method.getDescription();
		String indent1 = indent+ INDENT_INCREMENT;
		String indent2 = indent1+ INDENT_INCREMENT;
		try {
			writeTagString(httpType, indent, stream, false);
			if(nonEmptyString(description)){
				writeTagString(TAG_DESCRIPTION, indent1, stream, true);
				writePassage(description, indent2, stream);
			}
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		Body body = method.getBody();
		printBody(body,indent1,method,stream);
		
		LinkedHashMap<Integer, Response> responses = method.getResponses();
		if(!responses.isEmpty()){
			try {
				writeTagString(TAG_RESPONSES, indent1, stream, false);
				for(Response resp : responses.values()){
					printResponse(resp,indent2,stream);
				}				
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	private static boolean nonEmptyString(String str) {
		return str!=null&&!str.trim().isEmpty();
	}

	private static void printResponse(Response resp, String indent,OutputStream stream)
	{
		int code = resp.getCode();
		String indent1=indent+INDENT_INCREMENT;
		String indent2=indent1+INDENT_INCREMENT;
		try {
			writeTagString(""+code, indent, stream,false);
			String description = resp.getDescription();
			if(nonEmptyString(description)){
				writeTagString(TAG_DESCRIPTION, indent1, stream, true);
				writePassage(description, indent2, stream);
			}
			Body body = resp.getBody();
			if(!body.isEmpty())
				printBody(body, indent1, null, stream);
			
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}

	private static void printBody(Body body, String indent, Method method, OutputStream stream)
	{
		try {			
			writeTagString(TAG_BODY, indent, stream,false);
			boolean bodyParams=false;
			ArrayList<String> examples = body.getExamples();
			if(method!=null){
				for(String str:examples){
					if(!str.contains("-d '{"))
						continue;
					
					bodyParams=true;
					break;
				}
				LinkedHashMap<String, Param> params = method.getParams();
				if(!bodyParams&&!params.isEmpty()) {					
					indent+=INDENT_INCREMENT;
					writeTagString(TAG_WWW_FORM_URLENCODED, indent, stream, false);		
					printFormParams(params,indent+INDENT_INCREMENT,stream);
				}
			}
			
			String indent1=indent+INDENT_INCREMENT;
			String indent2=indent1+INDENT_INCREMENT;
			if(!examples.isEmpty()){
				writeTagString(TAG_SCHEMA, indent1, stream, true);
				writeTagString(TAG_EXAMPLE, indent1, stream, true);
				int size = examples.size();
				if(size>1){
					for(int i = 0 ; i < size ; i++){
						String ex = examples.get(i);
						writeString("example "+i+":", indent2, stream);
						writePassage(ex, indent2, stream);
						if(i<size-1)
							writePassage("", indent2, stream);
					}
				}
				else{
					writePassage(examples.get(0), indent2, stream);
				}
			}
			
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}

	private static void printFormParams( Map<String, Param> params,String indent, OutputStream stream)
	{
		String indent1= indent + INDENT_INCREMENT;
		try {
			writeTagString("formParameters", indent, stream, false);
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		for(Param param: params.values()){		
			printParam(param,indent1,stream);
		}
	}

	private static void printParam(Param param, String indent, OutputStream stream)
	{
		String name = param.getName();
		if(!nonEmptyString(name))
			return;
		
		String indent1 = indent + INDENT_INCREMENT;
		String type = param.getType();
		String description = param.getDescription();
		try {
			writeTagString(name, indent, stream, false);
			if(nonEmptyString(type)&&!type.toLowerCase().trim().equals("string"))
				writeKEValuePair("type", type, indent1, stream);
			if(nonEmptyString(description))
				writeKEValuePair(TAG_DESCRIPTION, description, indent1, stream);
			if(param.isReqired())
				writeKEValuePair(TAG_REQUIRED, "true", indent1, stream);
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}		
	}

	private static void writePassage(String str,String indent, OutputStream stream)
			throws IOException {
		
		BufferedReader br = new BufferedReader(new StringReader(str));
		StringBuilder bld = new StringBuilder();
		for( String line = br.readLine(); line != null; line = br.readLine()){
			bld.append(indent).append(line).append(lineSeparator);
		}
		String string = bld.toString();
		stream.write(string.getBytes("UTF-8"));
	}

	private static void writeTagString(String str, String indent, OutputStream stream, boolean multyLine)
			throws UnsupportedEncodingException, IOException {
		
		StringBuilder bld = new StringBuilder(str);
		bld.append(":");
		if(multyLine)
			bld.append(" |");
		
		writeString(bld.toString(), indent, stream);
	}
	private static void writeString(String str, String indentStr, OutputStream stream)
			throws UnsupportedEncodingException, IOException {
		
		StringBuilder bld = new StringBuilder(indentStr);
		bld.append(str);
		bld.append(lineSeparator);
		String string = bld.toString();
		stream.write(string.getBytes("UTF-8"));
	}
	
	private static void writeKEValuePair(String key, String value, String indent, OutputStream stream)
			throws UnsupportedEncodingException, IOException {
		
		StringBuilder bld = new StringBuilder(key);
		bld.append(": ");
		bld.append(value);		
		
		writeString(bld.toString(), indent, stream);
	}

}
